using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Xml;
using System.Web;

namespace Eve.Data
{
    /// <summary>
    /// Represents the method that executes when an exception occurs during automatic updating.
    /// </summary>
    /// <param name="query">The query that caused the exception.</param>
    /// <param name="e">The specific exception that was raised.</param>
    /// <returns><b>true</b> to stop processing the update, otherwise <b>false</b>.</returns>
    /// <remarks>
    /// <para>
    /// The <b>UpdateExceptionHandler</b> is invoked when <see cref="Eve.Data.EveData">EveData</see> encounters an exception during the
    /// automatic updating process.
    /// </para>
    /// </remarks>
    public delegate bool UpdateExceptionHandler(string query, Exception e);

    internal enum UpdateType
    {
        Unknown = 0,
        Schema = 1,
        Data = 2,
        Differential = 3,
    }
    internal enum CompressionType
    {
        Unknown = 0,
        None = 1,
        GZip = 2,
    }
    internal class Update
    {
        private UpdateType type;
        private string url;
        private int version;
        private DateTime date;
        private string notes;
        private CompressionType compressionType;

        public UpdateType Type
        {
            get { return type; }
        }
        public string URL
        {
            get { return url; }
        }
        public int Version
        {
            get { return version; }
        }
        public DateTime Date
        {
            get { return date; }
        }
        public string Notes
        {
            get { return notes; }
        }
        public CompressionType CompressionType
        {
            get { return compressionType; }
        }

        internal Update(XmlElement e)
        {
            switch (e.Name)
            {
                case "schemaUpdate": type = UpdateType.Schema; break;
                case "dataUpdate": type = UpdateType.Data; break;
                case "differentialUpdate": type = UpdateType.Differential; break;
                default: type = UpdateType.Unknown; break;
            }
            if (type == UpdateType.Unknown)
            {
                notes = e.OuterXml;
                return;
            }
            url = e.Attributes["url"].Value;
            version = int.Parse(e.Attributes["version"].Value);
            date = DateTime.Parse(e.Attributes["date"].Value);
            switch (e.Attributes["compression"].Value)
            {
                case "none": compressionType = CompressionType.None; break;
                case "gzip": compressionType = CompressionType.GZip; break;
                default: compressionType = CompressionType.Unknown; break;
            }
            notes = e.InnerText;
        }
    }

    /// <summary>
    /// Represents the current <b>EveData</b> database version.
    /// </summary>
    /// <remarks>
    /// <b>EveData</b> stores version information in the <c>dataVersion</c> table in the database.  This table is not required for the operation
    /// of the <see cref="Eve.Data.EveData">EveData</see> class, but is required in order to use the automatic updating facility.
    /// </remarks>
    public class Version
    {
        private int versionNumber;
        private DateTime versionDate;
        private DateTime updateDate;
        private string notes;

        /// <summary>
        /// Represents the current database version number.
        /// </summary>
        /// <value>The current database version number.</value>
        public int VersionNumber
        {
            get { return versionNumber; }
        }
        /// <summary>
        /// Represents the date and time at which the database update was generated.
        /// </summary>
        /// <value>The date and time at which the database update was generated.</value>
        public DateTime VersionDate
        {
            get { return versionDate; }
        }
        /// <summary>
        /// Represents the date and time at which the database update was performed.
        /// </summary>
        /// <value>The date and time at which the database update was performed.</value>
        public DateTime UpdateDate
        {
            get { return updateDate; }
        }
        /// <summary>
        /// Represents notes regarding a specific database version.
        /// </summary>
        /// <value>Notes regarding a specific database version.</value>
        public string Notes
        {
            get { return notes; }
        }

        internal Version(DbDataRow row)
        {
            versionNumber = row["versionID"];
            versionDate = row["versionDate"];
            updateDate = row["updated"];
            notes = row["notes"];
        }
    }

    /// <summary>
    /// The basis for all Eve data dump functionality.
    /// </summary>
    /// <remarks>
    /// <para>The <b>EveData</b> class provides a set of methods that allow access to all of the data contained within the Eve Official Data Dump.</para>
    /// 
    /// </remarks>
    public class EveData
    {
        private string updateURL = "http://www.evetoolbox.com/update/updatelist.xml";
        private Database db;

        public string DatabaseName
        {
            get
            {
                if (db != null) return db.Name;
                return "";
            }
        }
        public string ConnectionString
        {
            get { return db.ConnectionString; }
            set { db.ConnectionString = value; }
        }
        public Version Version
        {
            get
            {
                if (!db.TableExists("dataVersion")) return null;
                string query = "SELECT * FROM dataVersion ORDER BY versionID DESC";
                DbDataRow row = db.ExecuteSingle(query);
                if (row != null) return new Version(row);
                return null;
            }
        }

        public event UpdateExceptionHandler OnUpdateException
        {
            add { db.OnUpdateException += value; }
            remove { db.OnUpdateException -= value; }
        }

        public EveData(DatabaseType dbType, string dbConnectionString)
        {
            db = Database.Create(dbType, dbConnectionString);
        }

        public bool AutomaticUpdate()
        {
            try { ProcessUpdateList(GetUpdateList()); }
            catch (Exception e) { return db.UpdateException(null, e); }
            return true;
        }

        internal List<Update> GetUpdateList()
        {
            List<Update> ret = new List<Update>();
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(updateURL);
            req.Method = "GET";
            WebResponse wr = req.GetResponse();
            Stream s = wr.GetResponseStream();
            XmlDocument xd = new XmlDocument();
            xd.Load(s);

            if (xd["updates"] != null)
            {
                foreach (XmlElement row in xd["updates"].ChildNodes)
                    ret.Add(new Update(row));
            }
            s.Close();
            wr.Close();
            return ret;
        }
        internal bool ProcessUpdateList(List<Update> updates)
        {
            foreach (Update update in updates)
            {
                Version v = Version;
                if (update.Type == UpdateType.Unknown || update.CompressionType == CompressionType.Unknown) continue;
                if (v == null || v.VersionNumber < update.Version)
                {
                    if (!Update(update))
                        return false;
                }
            }
            return true;
        }
        internal bool Update(Update update)
        {
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(update.URL);
            req.Method = "GET";
            WebResponse wr = req.GetResponse();
            Stream s = wr.GetResponseStream();
            MemoryStream ms = new MemoryStream();
            byte[] buffer = new byte[4096];
            int read = 0;
            while ((read = s.Read(buffer, 0, 4096)) != 0)
                ms.Write(buffer, 0, read);
            ms.Position = 0;
            s.Close();
            wr.Close();
            if (update.CompressionType == CompressionType.GZip)
                s = new GZipStream(ms, CompressionMode.Decompress);
            else
                s = ms;
            bool ret = Update(s);
            if (ret)
            {
                string query = "INSERT INTO dataVersion(versionID, versionDate, updated, notes) VALUES(";
                query += update.Version;
                query += ", '" + update.Date.ToString("yyyy-MM-dd hh:mm:ss") + "'";
                query += ", '" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") + "'";
                query += ", '" + update.Notes + "'";
                query += ")";
                db.ExecuteNonQuery(query);
            }
            s.Close();
            return ret;
        }
        internal bool Update(Stream s)
        {
            if (db != null)
                return db.Update(s);
            return false;
        }

        public static EveObjectType GetObjectType(int id)
        {
            int type = id / 100000;
            if (type >= 100) type = (type / 100) * 100;
            return (EveObjectType)type;
        }
        internal static EveObject GetObject(Database db, int id)
        {
            try
            {
                switch (GetObjectType(id))
                {
                    case EveObjectType.Faction: return new Faction(db, id);
                    case EveObjectType.Corporation: return new Corporation(db, id);
                    case EveObjectType.Agent: return new Agent(db, id);
                    case EveObjectType.Region: return new Region(db, id);
                    case EveObjectType.Constellation: return new Constellation(db, id);
                    case EveObjectType.SolarSystem: return new SolarSystem(db, id);
                    case EveObjectType.Celestial: return new Celestial(db, id);
                    case EveObjectType.Station: return new Station(db, id);
                    default: return null;
                }
            }
            catch
            {
                return null;
            }
        }
        internal static EveProperty GetProperty(Database db, int id, EvePropertyType type)
        {
            try
            {
                switch (type)
                {
                    case EvePropertyType.Type: return new Type(db, id);
                    case EvePropertyType.AgentType: return new AgentType(db, id);
                    case EvePropertyType.Division: return new Division(db, id);
                    case EvePropertyType.Operation: return new Operation(db, id);
                    case EvePropertyType.StationType: return new StationType(db, id);
                    case EvePropertyType.Graphic: return new Graphic(db, id);
                    case EvePropertyType.CorporateActivity: return new CorporateActivity(db, id);
                    case EvePropertyType.Group: return new Group(db, id);
                    case EvePropertyType.Race: return new Race(db, id);
                    case EvePropertyType.Category: return new Category(db, id);
                    case EvePropertyType.MarketGroup: return new MarketGroup(db, id);
                    case EvePropertyType.Ancestry: return new Ancestry(db, id);
                    case EvePropertyType.Bloodline: return new Bloodline(db, id);
                    case EvePropertyType.AssemblyLine: return new AssemblyLine(db, id);
                    case EvePropertyType.AssemblyLineType: return new AssemblyLineType(db, id);
                    case EvePropertyType.CharacterAttribute: return new CharacterAttribute(db, id);
                    case EvePropertyType.Attribute: return new Attribute(db, id);
                    case EvePropertyType.BlueprintType: return new BlueprintType(db, id);
                    case EvePropertyType.Career: return new Career(db, id);
                    case EvePropertyType.CareerSpeciality: return new CareerSpeciality(db, id);
                    case EvePropertyType.ControlTowerResourcePurpose: return new ControlTowerResourcePurpose(db, id);
                    case EvePropertyType.Effect: return new Effect(db, id);
                    case EvePropertyType.Flag: return new Flag(db, id);
                    case EvePropertyType.InstallationType: return new InstallationType(db, id);
                    case EvePropertyType.Landmark: return new Landmark(db, id);
                    case EvePropertyType.MetaGroup: return new MetaGroup(db, id);
                    case EvePropertyType.RAMActivity: return new RAMActivity(db, id);
                    case EvePropertyType.RAMCompletedStatus: return new RAMCompletedStatus(db, id);
                    case EvePropertyType.School: return new School(db, id);
                    case EvePropertyType.Service: return new Service(db, id);
                    case EvePropertyType.Unit: return new Unit(db, id);
                    default: return null;
                }
            }
            catch
            {
                return null;
            }
        }

        public List<Faction> Factions
        {
            get
            {
                List<Faction> ret = new List<Faction>();
                string query = "SELECT * FROM chrFactions";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Faction(row));
                return ret;
            }
        }
        public List<Corporation> Corporations
        {
            get
            {
                List<Corporation> ret = new List<Corporation>();
                string query = "SELECT * FROM crpNPCCorporations";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Corporation(row));
                return ret;
            }
        }
        public List<Agent> Agents
        {
            get
            {
                List<Agent> ret = new List<Agent>();
                string query = "SELECT * FROM agtAgents";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Agent(row));
                return ret;
            }
        }
        public List<Region> Regions
        {
            get
            {
                List<Region> ret = new List<Region>();
                string query = "SELECT * FROM mapRegions";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Region(row));
                return ret;
            }
        }
        public List<Constellation> Constellations
        {
            get
            {
                List<Constellation> ret = new List<Constellation>();
                string query = "SELECT * FROM mapConstellations";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Constellation(row));
                return ret;
            }
        }
        public List<SolarSystem> SolarSystems
        {
            get
            {
                List<SolarSystem> ret = new List<SolarSystem>();
                string query = "SELECT * FROM mapSolarSystems";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new SolarSystem(row));
                return ret;
            }
        }
        public List<Celestial> Celestials
        {
            get
            {
                List<Celestial> ret = new List<Celestial>();
                string query = "SELECT * FROM mapCelestials";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Celestial(row));
                return ret;
            }
        }
        public List<Station> Stations
        {
            get
            {
                List<Station> ret = new List<Station>();
                string query = "SELECT * FROM staStations";
                foreach (DbDataRow row in db.ExecuteQuery(query))
                    ret.Add(new Station(row));
                return ret;
            }
        }

        public EveObject GetObject(int id)
        {
            return GetObject(db, id);
        }
        public EveProperty GetProperty(int id, EvePropertyType type)
        {
            return GetProperty(db, id, type);
        }

        public Type GetType(int id)
        {
            return (Type)GetProperty(id, EvePropertyType.Type);
        }

        public Faction GetFaction(int id)
        {
            if (GetObjectType(id) != EveObjectType.Faction) return null;
            return (Faction)GetObject(id);
        }
        public Corporation GetCorporation(int id)
        {
            if (GetObjectType(id) != EveObjectType.Corporation) return null;
            return (Corporation)GetObject(id);
        }
        public Agent GetAgent(int id)
        {
            if (GetObjectType(id) != EveObjectType.Agent) return null;
            return (Agent)GetObject(id);
        }
        public Region GetRegion(int id)
        {
            if (GetObjectType(id) != EveObjectType.Region) return null;
            return (Region)GetObject(id);
        }
        public Constellation GetConstellation(int id)
        {
            if (GetObjectType(id) != EveObjectType.Constellation) return null;
            return (Constellation)GetObject(id);
        }
        public SolarSystem GetSolarSystem(int id)
        {
            if (GetObjectType(id) != EveObjectType.SolarSystem) return null;
            return (SolarSystem)GetObject(id);
        }
        public Celestial GetCelestialt(int id)
        {
            if (GetObjectType(id) != EveObjectType.Celestial) return null;
            return (Celestial)GetObject(id);
        }
        public Station GetStation(int id)
        {
            if (GetObjectType(id) != EveObjectType.Station) return null;
            return (Station)GetObject(id);
        }

        public DbDataRowCollection ExecuteQuery(string query)
        {
            if (db != null) return db.ExecuteQuery(query);
            return null;
        }
        public int ExecuteNonQuery(string query)
        {
            if (db != null) return db.ExecuteNonQuery(query);
            return 0;
        }
        public DbObject ExecuteScalar(string query)
        {
            if (db != null) return db.ExecuteScalar(query);
            return null;
        }
    }
}
